import numpy as np
import matplotlib.pyplot as plt

def pure_cnvs_monte_carlo_integer_lambda(M_fragments=50, lambda_factor=2, rho_c=0.05, trials=5000):
    """
    Simulazione del Test Parametrico 1.
    devo usare SOLO lambda_factor interi (es. 2, 3, 4) per garantire la purezza del
    Coupon Collector's Problem senza approssimazioni altrimenti lo blocco.
    """
    # 1. CONTROLLO DI SICUREZZA: lambda_factor deve essere un intero
    if not isinstance(lambda_factor, int) or lambda_factor < 1:
        raise ValueError("ERRORE: lambda_factor deve essere un numero intero >= 1")

    # 2. CREAZIONE DEL POOL FISICO
    # Con lambda=2 e M=50, np.repeat crea l'array [0,0, 1,1, 2,2 ... 49,49]
    pool = np.repeat(np.arange(M_fragments), lambda_factor)
    N_total = len(pool)
    
    q_values = np.linspace(0.01, 0.99, 50) # Percentuale di nodi fisici compromessi
    p_success = []

    for q in q_values:
        wins = 0
        k_captured = int(round(q * N_total))
        
        for _ in range(trials):
            # 3. ATTACCO PRIMARIO: Estrazione FISICA senza rimpiazzo
            captured_nodes = np.random.choice(pool, size=k_captured, replace=False)
            
            # Quanti frammenti semantici UNICI ha davvero catturato l'attaccante?
            unique_captured = len(np.unique(captured_nodes))
            
            # Quanti frammenti mancano per vincere (Global Veto)?
            missing_fragments = M_fragments - unique_captured
            
            # 4. ATTACCO SECONDARIO (Inferenza Dipendente)
            p_inference = 1.0 - (1.0 - rho_c) ** unique_captured
            
            inferred_fragments = 0
            if missing_fragments > 0:
                # L'inferenza agisce SOLO sui frammenti mancanti
                inferred_fragments = np.random.binomial(missing_fragments, p_inference)
            
            # Conoscenza totale (Catturati fisicamente + Dedotti)
            total_known = unique_captured + inferred_fragments
            
            # 5. CONDIZIONE DI VITTORIA (Veto Globale Rispettato)
            if total_known >= M_fragments:
                wins += 1
                
        p_success.append(wins / trials)
        
    return q_values, p_success

# ============================================================
# ESECUZIONE DEL TEST
# ============================================================
plt.figure(figsize=(11, 7))

# Fissiamo il seed per riproducibilità scientifica
np.random.seed(20260525)

rho_values = [0.001, 0.01, 0.02, 0.03]
colors = ['green', 'blue', 'orange', 'red']

print("Esecuzione Test 1 (Lambda Intero) in corso. Attendi...")

for rho, color in zip(rho_values, colors):
    # ATTENZIONE: Passiamo esplicitamente lambda_factor=2 (intero)
    q_vals, p_vals = pure_cnvs_monte_carlo_integer_lambda(M_fragments=50, lambda_factor=2, rho_c=rho, trials=5000)
    plt.plot(q_vals, p_vals, color=color, linewidth=2.5, label=f'rho_c = {rho}')

# Linea di riferimento BFT
plt.axvline(x=1/3, color='black', linestyle='--', alpha=0.7, label='BFT Limit (1/3)')

# Etichette
plt.title("CNVS Parametric Test 1: Probability of False Positive (Integer Lambda)", fontsize=14, fontweight='bold')
plt.xlabel("Fraction of Initially Corrupted Nodes (q)", fontsize=12)
plt.ylabel("Attacker Win Probability (False Positive)", fontsize=12)
plt.legend(loc='lower right', fontsize=11)
plt.grid(True, linestyle=':', alpha=0.7)

plt.tight_layout()
plt.savefig("CNVS_Test_1_Integer_Lambda.png", dpi=200)
plt.show()

print("Test concluso. Grafico generato con successo.")